# -*- coding: utf-8 -*-
import KBEngine
import IKernel
from KBEDebug import *
import time

class Common:
	def __init__(self):
		self.willAddHeartbeat = {}
		self.isAlreadyInit = 1

		self.addTimer(0.1, 0.1, 0)
		IKernel.Instance().onEvent(self.getScriptName(), "init", self.id, self.id, [])

	def getScriptName(self):
		return self.__class__.__name__

	def isInit(self):
		if hasattr(self, "isAlreadyInit") == False:
			return False

		return self.isAlreadyInit == 1

	def onPropertyChanged(self, propName, oldValue):
		changedType = 0
		import ValChangedDef
		if oldValue == None:
			changedType = ValChangedDef.DATA_ADD
		else:
			changedType = ValChangedDef.DATA_UPDATE
		self.onValChanged(propName, oldValue, changedType)

	def onValChanged(self, propName, oldValue, changedType):
		cbInfo = None
		curEnv = KBEngine.component
		if curEnv == "baseapp" :
			cbInfo = self.propChangedCallback_bs
		elif curEnv == "cellapp" :
			cbInfo = self.propChangedCallback
		else:
			# 其他 app 目前不支持
			assert(False)
		propChangedCBs = cbInfo.get(propName)
		if propChangedCBs == None:
			return

		orderCBs = sorted(propChangedCBs.items(), key = lambda pair:pair[1], reverse = True)

		for orderCB in orderCBs:
			funcName = orderCB[0]
			funcAddr = IKernel.Instance().getFuncAddr(funcName)
			if funcAddr == None:
				DEBUG_MSG("funcName=%s no match funcAddr" % (funcName))
				continue
			funcAddr(self.id, propName, oldValue)

	def addPropChangedCallback(self, propName, funcName, priority = 0):
		if hasattr(self, propName) == False:
			ERROR_MSG("entityID=%d not has propName=%s" % (self.id, propName))
			return False

		cbInfo = None
		curEnv = KBEngine.component
		if curEnv == "baseapp" :
			cbInfo = self.propChangedCallback_bs
		elif curEnv == "cellapp" :
			cbInfo = self.propChangedCallback
		else:
			# 其他 app 目前不支持
			assert(False)
			return False

		propCb = cbInfo.get(propName)
		if propCb == None:
			cbInfo[propName] = {}
			propCb = cbInfo.get(propName)

		propCb[funcName] = priority

		return True

	def hasPropChangedCallback(self, propName, funcName):
		if hasattr(self, propName) == False:
			return False

		cbInfo = None
		curEnv = KBEngine.component
		if curEnv == "baseapp" :
			cbInfo = self.propChangedCallback_bs
		elif curEnv == "cellapp" :
			cbInfo = self.propChangedCallback
		else:
			# 其他 app 目前不支持
			assert(False)
			return False

		cbFunc = cbInfo.get(propName)
		if cbFunc == None:
			return False
		return (cbFunc.get(funcName) != None)

	def delPropChangedCallback(self, propName, funcName):
		if hasattr(self, propName) == False:
			ERROR_MSG("entityID=%d not has propName=%s" % (self.id, propName))
			return False

		cbInfo = None
		curEnv = KBEngine.component
		if curEnv == "baseapp" :
			cbInfo = self.propChangedCallback_bs
		elif curEnv == "cellapp" :
			cbInfo = self.propChangedCallback
		else:
			# 其他 app 目前不支持
			assert(False)
			return False

		cbFunc = cbInfo.get(propName)
		if cbFunc == None:
			return False
		return (cbFunc.pop(funcName) != None)

	def onTimer(self, id, userArg):
		"""
		KBEngine method.
		使用addTimer后， 当时间到达则该接口被调用
		@param id		: addTimer 的返回值ID
		@param userArg	: addTimer 最后一个参数所给入的数据
		"""
		self.tick()

	def tick(self):
		hbInfo = None
		curEnv = KBEngine.component
		if curEnv == "baseapp" :
			hbInfo = self.heartbeat_bs
		elif curEnv == "cellapp" :
			hbInfo = self.heartbeat
		else:
			# 其他 app 目前不支持
			assert(False)
			return

		# 这么做的原因是，防止在定时器回调中，继续增加定时器，造成报错
		# RuntimeError dictionary changed size during iteration
		for funcName, hb in self.willAddHeartbeat.items():
			funcName = hb[0]
			interval = hb[1]
			count = hb[2]
			cbInfo = {}
			cbInfo["funcName"] = funcName
			cbInfo["totalCount"] = count
			cbInfo["curCount"] = 0
			cbInfo["interval"] = interval
			cbInfo["nextTickTime"] = int(time.time() * 1000) + interval

			hbInfo[funcName] = cbInfo

		self.willAddHeartbeat = {}
		if len(hbInfo) <= 0:
			return

		# 将要移除的执行完的心跳
		willRemoveHB = []

		curMsec = int(time.time() * 1000)
		for funcName, cbInfo in hbInfo.items() :
			if curMsec < cbInfo["nextTickTime"]:
				continue
			cbInfo["nextTickTime"] = curMsec + cbInfo["interval"]

			cbInfo["curCount"] = cbInfo["curCount"] + 1
			if cbInfo["totalCount"] >= 0  and cbInfo["curCount"] >= cbInfo["totalCount"]:
				willRemoveHB.append(funcName)

			# 为0表示上次tick的时候，已经删除这个定时器了
			if cbInfo["totalCount"] == 0:
				continue

			funcName = cbInfo["funcName"]
			funcAddr = IKernel.Instance().getFuncAddr(funcName)
			if funcAddr == None:
				DEBUG_MSG("funcName=%s no match funcAddr" % (funcName))
				continue
			funcAddr(self.id, cbInfo["curCount"], cbInfo["interval"])

		for funcName in willRemoveHB:
			hbInfo.pop(funcName)

	def addHeartbeat(self, funcName, interval, count):
		if count == 0 or interval <= 0 or IKernel.Instance().getFuncAddr(funcName) == None:
			return False

		hbInfo = None
		curEnv = KBEngine.component
		if curEnv == "baseapp" :
			hbInfo = self.heartbeat_bs
		elif curEnv == "cellapp" :
			hbInfo = self.heartbeat
		else:
			# 其他 app 目前不支持
			assert(False)
			return False

		cbInfo = hbInfo.get(funcName)
		if cbInfo == None or cbInfo["totalCount"] == 0:
			self.willAddHeartbeat[funcName] = [funcName, interval, count]
			return True

		# 警告一下，已经有这个定时器了
		WARNING_MSG("entityID=%d already has heartbeat=%s totalCount=%d interval=%d, nextTickTime=%d" % 
			(self.id, funcName, hbInfo["totalCount"], hbInfo["interval"], hbInfo["nextTickTime"])
		)

		# 更新下定时器的信息为当前的信息
		cbInfo["funcName"] = funcName
		cbInfo["totalCount"] = count
		cbInfo["curCount"] = 0
		cbInfo["interval"] = interval
		cbInfo["nextTickTime"] = int(time.time() * 1000) + interval

		return True

	def hasHeartbeat(self, funcName):
		hbInfo  = None
		if curEnv == "baseapp" :
			hbInfo = entity.heartbeat
		elif curEnv == "cellapp" :
			hbInfo = entity.heartbeat_bs
		else:
			assert(False)

		cbInfo = hbInfo.get(funcName)
		if cbInfo == None or cbInfo["totalCount"] == 0:
			return False

		return True

	def delHeartbeat(self, funcName):
		cbInfo = None
		curEnv = KBEngine.component
		if curEnv == "baseapp" :
			cbInfo = self.heartbeat_bs.get(funcName)
		elif curEnv == "cellapp" :
			cbInfo = self.heartbeat.get(funcName)
		else:
			# 其他 app 目前不支持
			assert(False)
			return False

		if cbInfo == None:
			return False

		# 这一次tick不会删除，到下一次才真正删除
		cbInfo["totalCount"] = 0
		cbInfo["nextTickTime"] = 0
		return True

	def onCell2BaseMsg(self, senderEntityID, args):
		curEnv = KBEngine.component
		if curEnv != "baseapp":
			assert(False)
			return
		IKernel.Instance().onIntMsg(senderEntityID, self.id, args)

	def onBase2BaseMsg(self, senderEntityID, args):
		curEnv = KBEngine.component
		if curEnv != "baseapp":
			assert(False)
			return
		IKernel.Instance().onIntMsg(senderEntityID, self.id, args)

	def onBase2CellMsg(self, senderEntityID, args):
		curEnv = KBEngine.component
		if curEnv != "cellapp":
			assert(False)
			return
		IKernel.Instance().onIntMsg(senderEntityID, self.id, args)

	def onCell2CellMsg(self, senderEntityID, args):
		curEnv = KBEngine.component
		if curEnv != "cellapp":
			assert(False)
			return
		IKernel.Instance().onIntMsg(senderEntityID, self.id, args)

